package ua.com.softway.date;

import java.io.Serializable;
import java.text.ParseException;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class Date implements Serializable, Comparable<Date> {
	public final static int JAN = 1;
	public final static int FEB = 2;
	public final static int MAR = 3;
	public final static int APR = 4;
	public final static int MAY = 5;
	public final static int JUN = 6;
	public final static int JUL = 7;
	public final static int AUG = 8;
	public final static int SEP = 9;
	public final static int OCT = 10;
	public final static int NOV = 11;
	public final static int DEC = 12;

	public final static int SUN = 1;
	public final static int MON = 2;
	public final static int TUE = 3;
	public final static int WED = 4;
	public final static int THU = 5;
	public final static int FRI = 6;
	public final static int SAT = 7;
	
	private int day; // 24
	private int month; // 12
	private int year; // 2007

	public Date(int year, int month, int day) {
	    this.year = year;
	    this.month = month;
	    this.day = day;
	    
	    normalize();
	}
	
	public static void main(String[] args) {
        System.out.println(-22 % 10);
    }
	
	private void normalize() {
	    if (month > 0) {
	        year = year + (month - 1) / 12;
	        month = 1 + (month - 1) % 12;
	    } else {
	        year = year - (-month + 12) / 12;
	        month = 12 - (-month) % 12;
	        
	        // 0 - > 12
	        // -1 -> 11
	        // -2 -> 10
	        // -3 -> 9
	        // -4 -> 8
	        // -5 -> 7
	        // -6 -> 6
	        // -7 -> 5
	        // -8 -> 4
	        // -9 -> 3
	        // -10 - > 2
	        // -11 -> 1
	        // -12 -> 12
	        // -13 -> 11
	    }
        
        // day
        while (true) {
            if (day < 1) {
                if (-- month == 0) {
                    year --;
                    month = 12;
                }
                
                day = day + getDaysInMonth();
                
                continue;
            }
            
            int daysInMonth = getDaysInMonth();
            
            if (day > daysInMonth) {
                day -= daysInMonth;
                
                if (++ month == 13) {
                    year ++;
                    month = 1;
                }
                
                continue;
            }
            
            break;
        }
	}
	
	private final static int monthes[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	
	private final static transient int gregorianCutoverYear = 1582;
	
	private boolean isLeapYear() {
	    if ((year & 3) != 0) return false;

        if (year >= gregorianCutoverYear) return (year % 100 != 0) || (year % 400 == 0);
        
        return true; 
	}
	
	private int getDaysInMonth() {
	    if (month != 2) return monthes[month - 1];
	    
	    return isLeapYear() ? 29 : 28;
	}
	
	public Date(int year, int month) {
	    this(year, month, 1);
	}

	private Date(GregorianCalendar calendar) {
        year = calendar.get(GregorianCalendar.YEAR);
        month = calendar.get(GregorianCalendar.MONTH) + 1;
        day = calendar.get(GregorianCalendar.DAY_OF_MONTH);
	}

	// TODO reimplement
	public long getUTC() {
		return toJavaCalendar().getTimeInMillis();
	}

	public int getMonth() {
		return month;
	}

	public int getYear() {
		return year;
	}

	public int getDay() {
		return day;
	}

	public boolean after(Date d) {
		return compareTo(d) > 0;
	}

	public boolean before(Date d) {
		return compareTo(d) < 0;
	}

	/**
	 * Return difference days
	 */
	public int differenceInDays(Date d) {
		return (int) Math.round((getUTC() - d.getUTC()) / 24.0 / 60 / 60 / 1000);
	}

	public Date addDays(int days) {
		return new Date(year, month, day + days);
	}
	
	public Date addMonthes(int monthes) {
	    return new Date(year, month + monthes, day);
	}

    // TODO reimplement
	public int getDayOfWeek() {
		Calendar cal = toJavaCalendar();

		return cal.get(Calendar.DAY_OF_WEEK);
	}
	
    // TODO reimplement
	public int getWeekOfYear() {
        Calendar cal = toJavaCalendar();

        return cal.get(Calendar.WEEK_OF_YEAR);
	}

	private Calendar toJavaCalendar() {
        GregorianCalendar calendar = new GregorianCalendar();

        calendar.clear();
        
        calendar.set(GregorianCalendar.YEAR, year);
        calendar.set(GregorianCalendar.MONTH, month - 1);
        calendar.set(GregorianCalendar.DAY_OF_MONTH, day);
        
        return calendar;
	}

	public static Date today() {
		return new Date(new GregorianCalendar());
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + day;
		result = prime * result + month;
		result = prime * result + year;
		return result;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj) return true;
		if (obj == null) return false;
		
		if (!(obj instanceof Date))
			return false;
		
		final Date other = (Date) obj;
		
		if (day != other.day) return false;
		if (month != other.month) return false;
		if (year != other.year) return false;
		
		return true;
	}

	@Override
	public String toString() {
		return String.format("%04d.%02d.%02d", year, month, day);
	}

	public int compareTo(Date o) {
		if (year > o.year) return 1;
		if (year < o.year) return -1;

		if (month > o.month) return 1;
		if (month < o.month) return -1;

		if (day > o.day) return 1;
		if (day < o.day) return -1;

		return 0;
	}

	public java.util.Date toJavaDate() {
		return toJavaCalendar().getTime();
	}

	public static Date fromJavaDate(java.util.Date date) {
		GregorianCalendar calendar = new GregorianCalendar();
		calendar.setTime(date);

		return new Date(calendar);
	}
	
    public DateTime dateTime() {
        return new DateTime(year, month, day, 0, 0, 0, 0);
    }

	public Date yearAfter() {
		return new Date(year + 1, month, day);
	}

	public Date yearAgo() {
		return new Date(year - 1, month, day);
	}

	public Date weekAgo() {
		return addDays(-7);
	}

	public Date weekAfter() {
		return addDays(7);
	}

	public Date monthAfter() {
		return new Date(year, month + 1, day);
	}

	public Date yesterday() {
		return addDays(-1);
	}

	public Date tomorrow() {
		return addDays(1);
	}

	public Date monthAgo() {
		return new Date(year, month - 1, day);
	}

	public Date daysAgo(int i) {
		return addDays(-i);
	}

	public Date daysAfter(int i) {
		return addDays(i);
	}

	/**
	 * 
	 * @param begin inclusive
	 * @param end inclusive
	 * @return
	 */
	public boolean between(Date begin, Date end) {
	    if (begin.after(this)) return false;
	    if (end.before(this)) return false;
	    
	    return true;
	}
	
	public Date convert(String fromZone, String toZone) {
	    DatePattern convert = new DatePattern("yyyy-MM-dd HH:mm:ss S");
	    
	    String s = convert.format(this, toZone);
	    
	    try {
            return convert.parseDate(s, fromZone);
        } catch (ParseException e) {
            throw new RuntimeException(e); // shoud not happen
        }
	}

    public Date monthStart() {
        return addDays(1 - getDay());
    }
}
